﻿#region Copyright (C) 2008, 2009 StreamTv

/* 
 *      Copyright (C) 2008, 2009 StreamTv
 *      http://code.google.com/p/mpstreamtv/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TvControl;
using TvDatabase;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using StreamTv2;
using Microsoft.Win32;

namespace ChannelList
{
    public class Manager : IWebService
    {
        private IController controller;

        #region IWebService Members

        public string Name
        {
            get { return "ChannelList"; }
        }

        public int BufferSize(string clientId)
        {
            return 0x1000;
        }

        public void ClientDisconnected(string clientId)
        {
            // Not used.
        }

        public void HeartBeat(string clientId)
        {
            // Not used.
        }

        public bool HeatbeatRequired(string clientId)
        {
            return false;
        }

        public void Initialize(TvControl.IController tvController)
        {
            controller = tvController;
        }

        public ErrorCode Process(string clientId, StreamTv2.RequestDetails details, out Stream media, out string mime)
        {
            #region Load Settings
            bool tv = false;
            bool radio = false;
            //bool rtsp = false;
            string groupName = "";
            int startListingFrom = 0;
            int items = 0;

            string transformFormat = "rss";

            // List Radio Channels.
            if (!Boolean.TryParse(details.Query["radio"], out radio))
                radio = false;
            // List Tv Channels.
            if (!Boolean.TryParse(details.Query["tv"], out tv))
                tv = false;
            //// RTSP Url.
            //if (!Boolean.TryParse(details.Query["rtsp"], out rtsp))
            //    rtsp = false;
            // Channel Group to list.
            if (details.Query.AllKeys.Contains("group"))
                groupName = details.Query["group"];
            // First channel to start listing from.
            if (details.Query.AllKeys.Contains("index"))
                if (!int.TryParse(details.Query["index"], out startListingFrom))
                    startListingFrom = 0;
            // Number of channels per page.
            if (details.Query.AllKeys.Contains("count"))
                if (!int.TryParse(details.Query["count"], out items))
                    items = 0;
            // Output format.
            if (details.Query.AllKeys.Contains("format"))
                transformFormat = details.Query["format"];

            // remove slashes.
            groupName.Replace("\"", "");
            groupName.Replace("'", "");

            // If both are false assume both selected.
            if (!radio && !tv)
            {
                radio = true;
                tv = true;
            }

            string transformFile = String.Format(@"{0}\Team MediaPortal\MediaPortal TV Server\StreamTv\wwwroot\transform\ChannelList.{1}.xslt", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), transformFormat);
            #endregion

            int currentChannelCount = 0;

            // Generate the xml.
            MemoryStream output = new MemoryStream();

            XmlWriterSettings xmlSettings = new XmlWriterSettings();

            xmlSettings.Encoding = Encoding.Unicode;
            xmlSettings.ConformanceLevel = ConformanceLevel.Auto;
            xmlSettings.CheckCharacters = true;
            xmlSettings.Indent = true;
            XmlWriter writer = XmlWriter.Create(output, xmlSettings);
            #region Remove leading byte mask - for java clients.
            // Removes the leading byte that some reading tools have issues with (eg. java implemtations like PS3MediaServer).
            //writer.Flush();
            //output.Position = 0;
            #endregion
            XDocument xml;

            try
            {
                // Generate xml.
                xml = new XDocument(
                    new XDeclaration("1.0", "utf-16", "yes"),
                    new XElement("channellisting",
                        new XElement("information",
                            new XElement("title", "TvServer"),
                            new XElement("link", new UriBuilder("http", details.HostName, details.ServerPort, details.Query["channelListUrl"]).Uri.ToString()),
                            new XElement("description", "MediaPortal TvServer Channel Listing"),
                            new XElement("copyright", "Rights reserved until I have determined what needs to be put here."),
                            new XElement("ttl", 1),
                            new XElement("pubdate", DateTime.Now.ToString("r"))
                        ),
                        new XElement("channels",
                            from channel in Channel.ListAll() as List<Channel>
                            orderby channel.IdChannel
                            where tv && channel.IsTv && CheckChannelGroup(channel, groupName) && channel.IdChannel >= startListingFrom && CheckItemCount(items, ref currentChannelCount)
                            select new XElement("channel",
                                new XAttribute("type", "Television"),
                                new XElement("title", channel.DisplayName),
                                //new XElement("link", GetCompleteLink(channel.IdChannel, "http", details.HostName, details.ServerPort, details.Query["timeshiftUrl"], rtsp).Uri.ToString()),
                                new XElement("link", new UriBuilder("http", details.HostName, details.ServerPort, details.Query["timeshiftUrl"], String.Format("?ChannelId={0}", channel.IdChannel)).Uri.ToString()),
                                new XElement("description", GetDescription(channel)),
                                new XElement("guid",
                                    new XAttribute("ispermalink", false),
                                    new XText(Guid.NewGuid().ToString())
                                ),
                                new XElement("pubdate", DateTime.Now.ToString("r"))
                            ),
                            from channel in Channel.ListAll() as List<Channel>
                            orderby channel.IdChannel
                            where radio && channel.IsRadio && CheckChannelGroup(channel, groupName) && channel.IdChannel >= startListingFrom && CheckItemCount(items, ref currentChannelCount)
                            select new XElement("channel",
                                new XAttribute("type", "Radio"),
                                new XElement("title", channel.DisplayName),
                                //new XElement("link", GetCompleteLink(channel.IdChannel, "http", details.HostName, details.ServerPort, details.Query["timeshiftUrl"], rtsp).Uri.ToString()),
                                new XElement("link", new UriBuilder("http", details.HostName, details.ServerPort, details.Query["timeshiftUrl"], String.Format("?ChannelId={0}", channel.IdChannel)).Uri.ToString()),
                                new XElement("description", GetDescription(channel)),
                                new XElement("guid",
                                    new XAttribute("ispermalink", false),
                                    new XText(Guid.NewGuid().ToString())
                                ),
                                new XElement("pubdate", DateTime.Now.ToString("r"))
                            )
                        )
                    )
                );

                // Apply any xslt transformation required.
                if (File.Exists(String.Format(transformFile, transformFormat)))
                {
                    XslCompiledTransform transform = new XslCompiledTransform();

                    try
                    {
                        transform.Load(XmlReader.Create(String.Format(transformFile, transformFormat)));
                        transform.Transform(xml.CreateReader(), writer);
                    }
#if DEBUG
                    catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine("ChannelList Transform Failed: {0}", e.Message);
                    }
#else
                    catch (Exception e)
                    {
                        TvLibrary.Log.Log.Error("StreamTv: Plugin: ChannelList - Transform File Failed: {0}", e);

                        media = null;
                        mime = null;

                        return ErrorCode.InternalError;
                    }
#endif
                    writer.Close();
                    output.Position = 0;

                    media = output;
                    mime = GetMimeType(transformFormat);

                    return ErrorCode.Successful;
                }
                else
                {
                    xml.Save(writer);
                    writer.Close();
                    output.Position = 0;

                    media = output;
                    mime = GetMimeType("xml");

                    return ErrorCode.Successful;
                }
            }
#if DEBUG
            catch (Exception e)
            {
                // DEBUG
                System.Diagnostics.Debug.WriteLine("StreamTv ChannelList Class - Error: {0}", e.Message);
                System.Diagnostics.Debug.WriteLine("StreamTv ChannelList Class - StackTrace: {0}", e.StackTrace);
                throw new Exception("Problem generating xml.", e);
            }
#else
            catch (Exception)
            {
                media = null;
                mime = null;

                return ErrorCode.InternalError;
            }
#endif
        }

        #endregion

        #region Private methods
        /// <summary>
        /// Return the description of the channel.
        /// </summary>
        /// <param name="channelId">Number of the channel.</param>
        /// <returns>Description of channel.</returns>
        private String GetDescription(Channel channelInformation)
        {
            try
            {
                if (String.IsNullOrEmpty(channelInformation.CurrentProgram.Title))
                    return "";

                return channelInformation.CurrentProgram.Title;
            }
            catch (Exception)
            {
                return null;
            }
        } // GetDescription

        /// <summary>
        /// Check if the channel is in the group.
        /// </summary>
        /// <param name="channelInformation">Channel details.</param>
        /// <param name="group">Group to compare against.</param>
        /// <returns>True if a match or group is null.</returns>
        private Boolean CheckChannelGroup(Channel channelInformation, String group)
        {
            if (String.IsNullOrEmpty(group.Trim()))
                return true;

            TvBusinessLayer businessLayer = new TvBusinessLayer();

            ChannelGroup matchingGroup = businessLayer.GetGroupByName(group);

            if (matchingGroup != null)
            {
                if (matchingGroup.ReferringGroupMap().Count(o => o.IdChannel == channelInformation.IdChannel) >= 1)
                {
                    return true; // Channel exists in the group.
                }
                else
                {
                    return false; // Channel doesn't exist in the group.
                }
            }
            else
            {
                return true; // No matching group to that name, default show all.
            }
        } // CheckChannelGroup

        /// <summary>
        /// Generate the link for the xml.
        /// </summary>
        /// <param name="isTv">Is it a Tv Stream.</param>
        /// <param name="channelId">Channel Id.</param>
        /// <param name="settings">Output settings.</param>
        /// <param name="schema">Host type.</param>
        /// <example>HTTP, RTSP, MMS</example>
        /// <param name="hostName">Server hostname.</param>
        /// <param name="port">Server port.</param>
        /// <param name="rtsp">Is RTSP link.</param>
        /// <returns>Complete Uri.</returns>
        private UriBuilder GetCompleteLink(int channelId, String schema, String hostName, int port, String relativePath, Boolean rtsp)
        {
            if (rtsp)
            {
                return new UriBuilder(schema, hostName, port, relativePath, String.Format("?ChannelId={0}&rtsp={1}", channelId, rtsp));
            }
            else
            {
                return new UriBuilder(schema, hostName, port, relativePath, String.Format("?ChannelId={0}", channelId));
            }
        } // GetCompleteLink

        /// <summary>
        /// Check if enough items have been shown.
        /// </summary>
        /// <param name="numberToShow">Number of channels to show.</param>
        /// <param name="currentCount">Current count.</param>
        /// <returns>True if number of channels shown is less then the current count.</returns>
        private Boolean CheckItemCount(int numberToShow, ref int currentCount)
        {
            if (numberToShow == 0) // If zero list all channels.
            {
                return true;
            }

            if (numberToShow < currentCount)
            {
                currentCount++;
                return true;
            }

            return false;
        } // CheckItemCount

        /// <summary>
        /// Search the windows registry for the correct mime.
        /// </summary>
        /// <param name="sExtension">Extension to search for.</param>
        /// <returns>Mime</returns>
        /// <remarks>
        /// Taken from: http://dotnet.org.za/eduard/archive/2005/02/03/14027.aspx
        /// There could be performance problems using this though I don't expect many cocurrent calls so I expect it will be negligable.
        /// </remarks>
        private string GetMimeType(string sExtension)
        {
            string extension = sExtension.ToLower();
            RegistryKey key = Registry.ClassesRoot.OpenSubKey("MIME\\Database\\Content Type");
            foreach (string keyName in key.GetSubKeyNames())
            {
                RegistryKey temp = key.OpenSubKey(keyName);
                if (extension.Equals(temp.GetValue("Extension")))
                {
                    return keyName;
                }
            }

            // Ok well that failed.
            // So time to manually check against a few known possible types.
            switch (sExtension)
            {
                case "rss":
                    return "application/rss+xml";
                case "xml":
                    return "application/xml";
                case "html":
                    return "text/html";
                default:
                    return "unknown/unknown";
            }
        }

        #endregion
    }
}
